﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WFExtended.Framework.ServiceModel
{
    /// <summary>
    /// Extended Proxy
    /// </summary>
    /// <typeparam name="T">Service Contract</typeparam>
    public class ExtendedProxy<T> : RealProxy
    {
        private Type _proxyType;

        private T _innerProxy;

        private ChannelFactory<T> _factory;

        private int _retryCount;

        private TimeSpan _retryDelay;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExtendedProxy{T}"/> class.
        /// </summary>
        /// <param name="proxyType">Type of the proxy.</param>
        /// <param name="innerProxy">The inner proxy.</param>
        /// <param name="factory">The factory.</param>
        /// <param name="retryCount">The retry count.</param>
        /// <param name="retryDelay">The retry delay.</param>
        public ExtendedProxy(Type proxyType, T innerProxy, ChannelFactory<T> factory, int retryCount, TimeSpan retryDelay)
            : base(proxyType)
        {
            _proxyType = proxyType;
            _innerProxy = innerProxy;
            _factory = factory;
            _retryCount = retryCount;
            _retryDelay = retryDelay;

            if (_retryDelay == TimeSpan.Zero)
                _retryDelay = new TimeSpan(0, 0, 0,0,5);
        }

        /// <summary>
        /// Invokes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        public override IMessage Invoke(IMessage message)
        {
            var methodCall = message as IMethodCallMessage;
            var methodInfo = methodCall.MethodBase as MethodInfo;
            object result = null;
            Exception lastExc = null;

            try
            {
                for (int i = 0; i <= _retryCount; i++)
                {
                    try
                    {
                        result = Invoke(methodInfo, methodCall);
                        lastExc = null;
                        break;
                    }
                    catch (CommunicationException cExc)
                    {
                        lastExc = cExc;
                        _innerProxy = new ChannelFactory<T>(_factory.Endpoint).CreateChannel();
                        Thread.Sleep(_retryDelay);
                    }
                    catch (Exception exc)
                    {
                        _innerProxy = new ChannelFactory<T>(_factory.Endpoint).CreateChannel();
                        lastExc = exc;
                        Thread.Sleep(_retryDelay);
                    }
                }

                if (lastExc != null)
                {
                    _factory.Abort();
                    Exception exceptionToThrow;
                    if (lastExc.InnerException != null)
                        exceptionToThrow = lastExc.InnerException;
                    else
                        exceptionToThrow = lastExc;

                    ServiceErrorNotifier.NotifyErrror(exceptionToThrow);

                    throw exceptionToThrow;
                }

                return new ReturnMessage(
                      result,
                      null,
                      0,
                      methodCall.LogicalCallContext,
                      methodCall);
            }
            catch (Exception e)
            {
                return new ReturnMessage(e, methodCall);
            }
        }

        internal object Invoke(MethodInfo methodInfo, IMethodCallMessage methodCall)
        {
            return methodInfo.Invoke(_innerProxy, methodCall.InArgs);
        }

        /// <summary>
        /// Gets the state.
        /// </summary>
        /// <value>
        /// The state.
        /// </value>
        public CommunicationState State
        {
            get { return _factory.State; }
        }

        /// <summary>
        /// Closes this instance.
        /// </summary>
        public void Close()
        {
            _factory.Close();
        }
    }
}
